package com.svconnect.plugin.views;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import com.svconnect.plugin.Activator;
import com.svconnect.plugin.builders.ServiceReferenceBuilder;
import com.svconnect.plugin.model.wsdl.Operation;
import com.svconnect.plugin.model.wsdl.Service;
import com.svconnect.plugin.preferences.PreferenceConstants;
import com.svconnect.plugin.readers.WSDLParser;
import com.svconnect.plugin.util.Log;

public class AddReferenceDialog extends Dialog {

	Text addressText;
	Text packageNameText;
	Tree servicesTree;
	Label statusLbl;
	ProgressBar progressBar;
	Button goButton;

	List<Service> services = null;
	String serviceUrl = null;

	private IProject project;

	public AddReferenceDialog(Shell parentShell, IProject project) {
		super(parentShell);
		this.project = project;

	}

	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.setText("Add Service Reference");
	}

	@Override
	protected Control createContents(Composite parent) {
		Composite composite = new Composite(parent, SWT.NULL);
		GridLayout gl = new GridLayout();
		composite.setLayout(gl);

		Label lbl = new Label(composite, SWT.LEFT);
		lbl.setText("To see a list of available services on a specific server, enter a service URL and click Go.");

		lbl = new Label(composite, SWT.LEFT);
		lbl.setText("Address:");

		Composite c = new Composite(composite, SWT.NULL);
		RowLayout rl = new RowLayout();
		c.setLayout(rl);
		addressText = new Text(c, SWT.SINGLE | SWT.BORDER);
		addressText.setLayoutData(new RowData(500, 20));
		goButton = new Button(c, SWT.PUSH);
		goButton.setText("Go");
		goButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				discoverService();
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});
		goButton.setLayoutData(new RowData(50, 30));

		lbl = new Label(composite, SWT.LEFT);
		lbl.setText("Services:");

		servicesTree = new Tree(composite, SWT.SINGLE | SWT.BORDER);
		GridData gd = new GridData(500, 200);
		gd.horizontalAlignment = SWT.FILL;
		gd.grabExcessHorizontalSpace = true;
		servicesTree.setLayoutData(gd);

		gd = new GridData(500, 20);
		gd.horizontalAlignment = SWT.FILL;
		gd.grabExcessHorizontalSpace = true;
		statusLbl = new Label(composite, SWT.LEFT);
		statusLbl.setLayoutData(gd);

		progressBar = new ProgressBar(composite, SWT.INDETERMINATE);
		progressBar.setLayoutData(gd);
		progressBar.setVisible(false);

		lbl = new Label(composite, SWT.LEFT);
		lbl.setText("Package name:");

		packageNameText = new Text(composite, SWT.SINGLE | SWT.BORDER);
		packageNameText.setLayoutData(gd);

		Control content = super.createContents(parent);
		this.getButton(OK).setEnabled(false);
		return content;
	}

	@Override
	protected void okPressed() {
		this.addReference();
	}

	public void discoverService() {
		if (addressText.getText().isEmpty()) {
			MessageDialog.openInformation(getShell(), "Information",
					"Please enter the address for a service.");
			return;
		}

		serviceUrl = addressText.getText();
		progressBar.setVisible(true);
		progressBar.update();
		statusLbl
				.setText("Please wait for service information to be downloaded.");
		goButton.setEnabled(false);
		getButton(OK).setEnabled(false);
		servicesTree.removeAll();

		Thread task = new Thread() {
			@Override
			public void run() {
				try {

					WSDLParser parser = new WSDLParser(fixWsdlUrl(serviceUrl));
					services = parser.parse();
					Thread.sleep(1000);

					Display.getDefault().asyncExec(new Runnable() {
						@Override
						public void run() {
							progressBar.setVisible(false);
							progressBar.update();
							goButton.setEnabled(true);
							getButton(OK).setEnabled(true);
							displayServices(services);
						}
					});

				} 
				catch (Exception e) {
					services = null;
					serviceUrl = null;
					Display.getDefault().asyncExec(new Runnable() {
						@Override
						public void run() {
							progressBar.setVisible(false);
							progressBar.update();
							statusLbl.setText("An error occurred while attempting to find services.");
							goButton.setEnabled(true);
							MessageDialog
									.openError(
											getShell(),
											"Error",
											"There was an error downloading metadata from the address. Please verify that you have entered a valid address.");
						}
					});
					Log.e(e);
				}
			}

		};

		task.start();
	}

	public void displayServices(List<Service> serviceList) {
		if (serviceList != null) {
			statusLbl.setText(String.format(
					"%d service(s) found at address '%s'", serviceList.size(),
					this.serviceUrl));

			Image svcImage = Activator.getImageDescriptor(
					"./icons/svc_if_icon.png").createImage();
			Image opImage = Activator.getImageDescriptor(
					"./icons/svc_op_icon.png").createImage();

			for (Service svc : serviceList) {
				TreeItem svcItem = new TreeItem(this.servicesTree, 0);
				svcItem.setImage(svcImage);

				svcItem.setText(String.format("I%s", svc.getName()));
				for (Operation op : svc.getOperations()) {
					TreeItem opItem = new TreeItem(svcItem, 0);
					opItem.setImage(opImage);
					opItem.setText(op.getName());
				}
			}
		}
	}

	public void addReference() {

		final String buildPath = Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.P_BUILD_PATH);
		boolean javaConvEnabled = Activator.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.P_JAVA_CONV_ENABLED);
		String referenceFilesDir = String.format("%s/%s", project.getLocation()
				.toString(), buildPath);		
		final boolean alreadyDiscovered = (this.services != null);
		
		if (!alreadyDiscovered) {
			String svcUrl = addressText.getText();
			if (svcUrl.isEmpty()) {
				MessageDialog.openInformation(getShell(), "Information",
						"Please enter the address for a service.");
				return;
			}
			serviceUrl = svcUrl;
		}

		String packageName = packageNameText.getText();
		if (!validPackageName(packageName)) {
			MessageDialog.openInformation(getShell(), "Information",
					"Please enter a valid package name.");
			return;
		}
		
		if (alreadyDiscovered) {
			// check service folders
			List<String> conflictServicesNames = new ArrayList<String>();
			StringBuilder sb = new StringBuilder();
			sb.append("The following service references already exist:\n");
			boolean hasAmbiguousServices = false;
			for(Service svc : services) {
				String projectFullPath = String.format("%s/%s", referenceFilesDir, svc.getName());
				File file = new File(projectFullPath);
				if (file.exists() && file.isDirectory()) {
					sb.append(svc.getName()+" ");
					conflictServicesNames.add(svc.getName());
					hasAmbiguousServices = true;
				}
			}
			sb.append("\n\nDo you want to merge service references?\n");
			sb.append("Any repeated data will be replaced.");
				
			if (hasAmbiguousServices) {
				boolean confirmed = MessageDialog.openConfirm(getShell(), "Add Service Reference", 
						sb.toString());
				if (!confirmed) return;
			}
			
			// check service packages
			conflictServicesNames = new ArrayList<String>();
			boolean hasAmbiguousPackages = false;
			File dir = new File(referenceFilesDir);
			sb = new StringBuilder();
			sb.append("The following service references already contains ");
			sb.append(packageName);
			sb.append(" package:\n");
			String packagePath = packageName.replace(".", "/");
			if (dir.exists() && dir.isDirectory()) {
				File[] files = dir.listFiles();
				for(File f : files) {
					if (f.isDirectory()) {
						String fullPackagePath = String.format("%s/%s/src/%s", referenceFilesDir, f.getName(), packagePath);
						File packageDir = new File(fullPackagePath);
						if (packageDir.exists() && packageDir.isDirectory()) {
							hasAmbiguousPackages = true;
							conflictServicesNames.add(f.getName());
							sb.append(f.getName()+" ");
						}
					}
				}
			}
			sb.append("\n\nAdding a new service reference may cause class name ambiguity.\n");
			sb.append("Do you want to continue?\n");
			
			if (hasAmbiguousPackages) {
				boolean confirmed = MessageDialog.openConfirm(getShell(), "Add Service Reference", 
						sb.toString());
				if (!confirmed) return;
			}
		}
		
		final ServiceReferenceBuilder srb = new ServiceReferenceBuilder(
				fixWsdlUrl(serviceUrl), packageName, packageName,
				referenceFilesDir);
		srb.setJavaConventionEnabled(javaConvEnabled);

		progressBar.setVisible(true);
		progressBar.update();
		statusLbl.setText("Creating new service reference to " + serviceUrl
				+ ".");
		goButton.setEnabled(false);
		getButton(OK).setEnabled(false);

		Thread thread = new Thread() {
			@Override
			public void run() {
				final boolean success = (alreadyDiscovered) ? srb
						.build(services) : srb.build();

				for (Service svc : services) {
					String path = String
							.format("%s/%s/%s/src", project.getFullPath()
									.toString(), buildPath, svc.getName());
					addClasspathEntry(project, path);
				}

				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}

				try {
					project.refreshLocal(IResource.DEPTH_INFINITE, null);
				} catch (CoreException e) {
					e.printStackTrace();
				}

				Display.getDefault().asyncExec(new Runnable() {
					@Override
					public void run() {
						if (success) {
							setReturnCode(OK);
							close();
						} else {
							progressBar.setVisible(false);
							progressBar.update();
							String msg = "An error occurred during creating service reference.";
							MessageDialog.openError(getShell(), "Error", msg);
							statusLbl.setText(msg);
							goButton.setEnabled(true);
							getButton(OK).setEnabled(true);
						}

					}
				});

			}
		};
		thread.start();
	}

	private boolean addClasspathEntry(IProject project, String cp) {
		try {
			IJavaProject javaProject = JavaCore.create(project);
			IClasspathEntry[] entries = javaProject.getRawClasspath();
			IClasspathEntry[] newEntries = new IClasspathEntry[entries.length + 1];
			System.arraycopy(entries, 0, newEntries, 0, entries.length);
			newEntries[entries.length] = JavaCore.newSourceEntry(new Path(cp));
			javaProject.setRawClasspath(newEntries, null);
			return true;

		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

	}

	public boolean validPackageName(String packageName) {
		String regex = "([a-z_]{1}[a-z0-9_]*(\\.[a-z_]{1}[a-z0-9_]*)*)";
		return packageName.matches(regex);
	}

	private String fixWsdlUrl(String url) {
		if (url.endsWith(".svc")) {
			return url + "?wsdl";
		}
		return url;
	}

}
